#ifndef TERN_REPLAYER_H
#define TERN_REPLAYER_H

//#include "ReplayerSetting.h"
#include "tern/TernSched.h"
#include "tern/SchedCache.h"
#include "tern/Sync.h"
#include "semaphore.h"

// MACRO definitions that will affect the performance and hit rate of the Replayer module. 
#define LANDMARK_FILE (get_option(ece, landmark_file))  
#define MAX_WINDOW_NUM 1000
#define MYSQLD_WINDOW_CHANCES 100
#define HTTPD_WINDOW_CHANCES 1

#define RESULT(args...) {fprintf(stderr, args); fflush(stderr);}
#define replayer_debug(args...) //{fprintf(stderr, args); fflush(stderr);}

using namespace llvm;
using namespace klee;
using namespace klee::expr;
using namespace std;

// Interface functions for applications. 
#ifdef __cplusplus
extern "C" {
#endif
void tern_symbolic(void *addr, unsigned nbytes, const char *name);
#ifdef __cplusplus
}
#endif

// extern functions.
extern pid_t gettid();

class WaitingThread {	
private:
	sem_t sem;
	unsigned id;
	volatile bool waitFlag;

public:	
	WaitingThread(unsigned i) {
		sem_init(&sem, 0, 0);
		id = i;
		waitFlag = false;
	}

	void reset() {
		if (REPLAYER_USE_WAIT_FLAG != 1) {
			sem_init(&sem, 0, 0);
		} else
			waitFlag = false;
	}

	inline int block() {

		int result = 0;

		if (REPLAYER_USE_WAIT_FLAG != 1) {
		    /* Convert from timeval to timespec */
			struct timespec ts;
			struct timeval te;
			long interval =       get_option(tern, replayer_block_timeout_interval);					// 1e8 ns, 0.1 s.
			const long sec1 = 1000000000;
 
			gettimeofday(&te, NULL);

			if (!IS_BATCH_PROGRAM) {
				ts.tv_sec  = te.tv_sec;
				ts.tv_nsec = te.tv_usec*1000;
				if (ts.tv_nsec + interval >= sec1) {
					ts.tv_sec++;
					ts.tv_nsec = ts.tv_nsec + interval - sec1;
				} else {
					ts.tv_nsec = ts.tv_nsec + interval;
				}
			} else {
				ts.tv_sec  = te.tv_sec + 4;						// For batch program, 4 seconds.
				ts.tv_nsec = te.tv_usec*1000;
			}
			result = sem_timedwait(&sem, &ts);
		} else {
			while (!waitFlag) {
				//asm("pause");
				//sched_yield();
			}
			waitFlag = false;
		}
		return result;
	}

	inline void signal(int replayTid) {
		if (REPLAYER_USE_WAIT_FLAG != 1) {
			sem_post(&sem);
		} else {
			waitFlag = true;
		}
	}
};

namespace klee {
	struct ReplayStat {
		// Statistics
		long taskNum;
		long finishNum;
		long taskMissedNum;
		long brokenNum;
		double avgRespTime;
		int windowHittedTimes[MAX_WINDOW_NUM];
		double windowFinishedTimes[MAX_WINDOW_NUM];
		int windowBrokenTimes[MAX_WINDOW_NUM];
		int windowTimeoutTimes[MAX_WINDOW_NUM];
		struct timeval startss[PROCESS_NUM];
		struct timeval endss[PROCESS_NUM];
	};

	class Replayer: public TernSched {
	private:
		/* Read-only data structures or POSIX sync objects. These are race free. */
		SchedCache *schedCache;
		pthread_cond_t taskStartCond;										// Wait for the current window to finish.
		pthread_spinlock_t sLock;
		spin_rwlock_t sRwLock;
		pthread_rwlock_t pRwLock;
		CacheItr *itr;																		// Race can be avoided by using atomic operations within the itr.

		/* Race-free arrays. */
		class WaitingThread *waitQueue[MAX_THREAD_NUM];
		pthread_cond_t *condVarForTracks[MAX_THREAD_NUM];
		int isInTask[MAX_THREAD_NUM];	

		/* Racy complex data structures. Use spinlock to protect them. */
		Int_To_Int_Map replayTids;
		volatile int replayIds;

		/* Flags that will only be invovled at taskStart/taskEnd. These are already protected by replayer mutex. */
		volatile unsigned inBatchNum;
		volatile unsigned outBatchNum;
		Int_To_Int_Map rpEnterTaskTidOrder;
		volatile bool admitFlag;
		/* The broken flag for current batch, since there may not be full number of tasks in the batch when the batch is broken (e.g., no schedule),
			we need this flag for the taskStart function to determine whether a new task is an with-order task or a wild task.
			This flag is protected by the replayer mutex. */
		volatile bool brokenFlag;
		volatile bool updateFlag;
		unsigned trackFailTries;
		unsigned numWaiting;
		Int_To_Int_Map rpTidToTmplTidMap;
		Int_To_Int_Map rpTidToBatchIDMap;
		std::vector<unsigned> batchIDList;

		// Stat, protected by mutex of replayer.
		struct ReplayStat replayStat;

		// Jingyue: for online record/replay
		int argc;
		char **argv;
		vector<string> inputBuffer;
		int numFailures;
		int forwardToRecorder();
		void reloadSchedCache();

	protected:
		void printStatistics();
		void initGlobals();
		void initWaitQueue();
		void resetWaitQueue();
		void wakeUpAllInWaitQueue();
		void printUsage();
		void printTids(int *order, unsigned length);
		void removeTidFromAllWindows(int tid);
		void generateCachedConstraints(const char *cacheName);
		void moveOnItr(int replayTid, int hookId, unsigned arg, unsigned dbgNum = 0, SchedTrunk *nextTrunk = NULL);
		void waitForAdmitTask();
		void admitOneTask(int callSite);
		void checkExceptionalTasks();
		void spinLock();
		void spinUnLock();

		void realSpinLockRead(int callSite = 0);
		void realSpinUnLockRead(int callSite = 0);
		void realSpinLock(int callSite = 0);
		void realSpinUnLock(int callSite = 0);
		void markBroken(unsigned arg, int hookId, int callSite);
		bool hasSchedCache();

		void startUpdate(int broken = 0);
		void endUpdate(int broken = 0);

		bool matchTrack(void *addr, unsigned nBytes, const char *symVarName);
		void waitForAdmitTaskAndCheck();
		void prepareForBatch();
	
	public:
		Replayer(SchedCache *sc, CacheItr *cItr);
		
		virtual void appStart();
		virtual void appEnd(int *info);

		virtual void taskStart(int *winNum, int instId, int hookId, void *addr, unsigned nBytes, const char *symVarName);
		virtual int taskEnd(int *winNum, int instId, int hookId);

		virtual void threadCreateStart(unsigned arg);
		virtual void threadCreateEnd(unsigned arg, int pthreadId);

		// Replayer does not need to handle this.
		virtual void threadExit(int *info, int hookId, bool realExit = 0) {}				

		virtual void symbolic(void *addr, unsigned nbytes, const char *name);

		virtual void beforeLandmark(unsigned arg0, unsigned hook_id, int instId, uint64_t arg1, uint64_t arg2, uint64_t arg3/*, std::string callStackStr*/);
		virtual void afterLandmark(pthread_t *tid, unsigned arg0, unsigned hook_id, int instId, uint64_t ret_val, uint64_t arg1, uint64_t arg2, uint64_t arg3/*, std::string callStackStr*/);

		virtual void waitForTurn(const char *landmarkName, unsigned arg, int hookId = -1);
		virtual void giveUpTurn(const char *landmarkName, unsigned arg);

		bool mutexAvailable(pthread_mutex_t *mutexAddr);
		bool noSched();
		void setNoSched();
		bool threadInTask(int replayTid, bool needLock, unsigned callSite);
		void initItr();
		inline int tid();
		void disableAllTasks();
		void waitUntilThreadCreateEnd();
		int rpEnterTaskOrder();
		// Jingyue
		void setProgramArguments(int argc, char **argv);
		void runRecordOnFailure();
		void appendInputBuffer(char *input);
		// end Jingyue
		int checkTrackAt(SchedTrack *track, unsigned trackId, std::vector< std::vector<unsigned char> > &values);
		inline void calStatStart();
		inline void calStatEnd();
	};
}

#endif

